import numpy as np
import numpy.linalg as LA
from scipy.sparse import linalg as sLA 
from scipy import sparse

def get_degrees_list(G):
    degrees = list()
    for node in G.nodes():
        d = G.degree(node)
        degrees.append(d)
    return degrees

def cn(A):
    M = sparse.csr_matrix(A)
    paths = M**2
    paths = paths.toarray()
    S = paths
    return S

def salton(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i] 
            j_degree = degrees[j] 
            factor = i_degree * j_degree
            if factor != 0:
                S[i,j] = (1.0/np.sqrt(factor)) * paths[i,j]
    return S

def jacard(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            if j != i:
                i_degree = degrees[i] 
                j_degree = degrees[j] 
                factor = i_degree + j_degree - paths[i,j]
                if factor != 0:
                    S[i,j] = (1.0/factor) * paths[i,j]
                
    return S

def sorensen(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i] 
            j_degree = degrees[j] 
            factor = i_degree + j_degree
            if factor != 0:
                S[i,j] = (2.0/factor) * paths[i,j]
                
    return S

def hpi(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i] 
            j_degree = degrees[j] 
            factor = np.min((i_degree, j_degree))
            if factor != 0:
                S[i,j] = (2.0/factor) * paths[i,j]
                
    return S

def hdi(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i]
            j_degree = degrees[j]
            factor = np.max((i_degree, j_degree))
            if factor != 0:
                S[i,j] = (2.0/factor) * paths[i,j]
                
    return S

def lhn1(A, degrees):
    N = A.shape[0]
    paths = cn(A)
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i] 
            j_degree = degrees[j] 
            factor = i_degree * j_degree
            if factor != 0:
                S[i,j] = (1.0/factor) * paths[i,j]
                
    return S

def pa(A, degrees):
    N = A.shape[0]
    S = np.zeros((N,N))
    
    for i in xrange(N):
        for j in xrange(N):
            i_degree = degrees[i] 
            j_degree = degrees[j] 
            factor = i_degree * j_degree
            S[i,j] = factor
                
    return S

def katz(A, h=0.1):
    """
        Calculates the Katz index.
        
        Parameters:
        graph: the graph.
        h: lambda, the normalization factor that must be 0 < h < 1
    """
    N = A.shape[0]
    I = np.identity(N)
    to_inv = (I - h*A)
    S = np.linalg.inv(to_inv) - I
    return S

def katz_h(A):
    """"
        Computes the reciprocal of the largest eigenvalue of the adjacency matrix of the graph.
        Theiprocal should be decreased so the reduced value can be used as the h parameter
        to compute the katz index.
    """
    A = sparse.csr_matrix(A)
    eigvals = sLA.eigsh(A, which='LM', return_eigenvectors=False)
    h = np.sort(eigvals)[-1]
    h = 1.0/h
    return h

def aa(A, degrees):
    """
    Parameters:
        A: numpy 2D adjacency array of the graph.
        degrees: a python list that contains the 
                 degree of each node index.
                 
    Returns: S the similarity matrix.
    """
    N = A.shape[0]
    #D is the degree matrix where D_(i,i) is the degree
    # of node i and zero elsewhere.
    D = np.zeros((N,N)) 
    for i in xrange(N):
        deg = degrees[i]
        if deg > 1:
            D[i,i] = 1.0/np.log10(deg)
            
    S = np.dot(A, D)
    S = np.dot(S, A)
    return S

def ra(A, degrees):
    """
    Parameters:
        A: numpy 2D adjacency array of the graph.
        degrees: a python list that contains the 
                 degree of each node index.
                 
    Returns: S the similarity matrix.
    """
    N = A.shape[0]
    # the degree matrix where D_(i,i) is the degree of node i and zero elsewhere.
    D = np.zeros((N,N)) 
    for i in xrange(N):
        deg = degrees[i]
        if deg > 1:
            D[i,i] = 1.0/deg
            
    S = np.dot(A, D)
    S = np.dot(S, A)
    return S

def lp(A, h=0.1):
    """
        Calculates the Local Path Index.
        
        Parameters:
        h: lambda, the katz normalization factor.
        
    """ 
    M = sparse.csr_matrix(A)
    paths_len2 = M**2
    paths_len3 = M**3
    S = paths_len2 + (h * paths_len3)
    S = S.toarray()
    
    return S









